package org.aldar.bquest.logic.controller.entity;

import android.util.Log;
import org.aldar.bquest.constants.IConstants;
import org.aldar.bquest.entity.HeroSprite;
import org.aldar.bquest.entity.effect.ParticleUtil;
import org.aldar.bquest.logic.TilesGameCycle;
import org.aldar.bquest.logic.controller.board.TouchAndMoveController;
import org.aldar.bquest.logic.controller.level.LevelController;
import org.aldar.bquest.logic.controller.level.TimeLine;
import org.aldar.bquest.resources.ResourceManager;
import org.aldar.bquest.screens.MainBoardScreen;
import org.aldar.bquest.util.Logger;
import org.andengine.engine.handler.timer.ITimerCallback;
import org.andengine.engine.handler.timer.TimerHandler;
import org.andengine.entity.IEntity;
import org.andengine.entity.modifier.DelayModifier;
import org.andengine.entity.modifier.IEntityModifier;
import org.andengine.entity.modifier.MoveXModifier;
import org.andengine.entity.sprite.AnimatedSprite;
import org.andengine.util.math.MathUtils;
import org.andengine.util.modifier.IModifier;

/**
 * User: Daria
 * Date: 8/26/13 11:42 PM
 */
public class HeroAnimationController implements IConstants {

    private static final Logger LOG = new Logger(HeroAnimationController.class);
    public static final float HERO_STEP_DURATION = 0.8f;
    public static final int WALKING_HERO_FRAMES = 25;

    private AnimatedSprite heroMovingSprite = null;
    private AnimatedSprite[] heroIdleSprite = null;
    private AnimatedSprite heroShootingSprite = null;

    private int stepsPending = 0;
    private int currentIdleIndex = 0;
    private int idleSequence = 0;

    private boolean isWalking = false;

    private static HeroAnimationController instance;
    private boolean isShooting;
    private boolean isPlayingConcert;
    private AnimatedSprite heroConcertSprite;

    private Runnable abilityActionRequested;

    public HeroAnimationController() {
        instance = this;
    }

    public static HeroAnimationController getInstance() {
        return instance;
    }

    public boolean isHeroShooting() {
        return isShooting;
    }

    public boolean isHeroPlayingConcert() {
        return isPlayingConcert;
    }

    public void initHero() {
        if (heroMovingSprite == null) {
            createHeroMovingSprite();
        }
        heroMovingSprite.setPosition(-50, GROUND_Y);
        isWalking = false;
    }

    private void createHeroMovingSprite() {
        heroMovingSprite = new HeroSprite(-50, GROUND_Y, ResourceManager.getInstance().getHeroMovingRegion(), MainBoardScreen.getScm().getVertexBufferObjectManager());
        heroMovingSprite.setAnchorCenter(0.5f, 0);
        heroMovingSprite.animate(40);
        heroMovingSprite.setAlpha(0);
        MainBoardScreen.getScm().getHeroLayer().attachChild(heroMovingSprite);

        heroShootingSprite = new AnimatedSprite(0, GROUND_Y, ResourceManager.getInstance().getHeroShootingAnimation(), MainBoardScreen.getScm().getVertexBufferObjectManager());
        heroShootingSprite.setAnchorCenter(0.37f, 0);

        heroConcertSprite = new AnimatedSprite(0, GROUND_Y, ResourceManager.getInstance().getHeroConcertAnimation(), MainBoardScreen.getScm().getVertexBufferObjectManager());
        heroConcertSprite.setAnchorCenter(0.4f, 0);

        heroIdleSprite = new AnimatedSprite[4];
        heroIdleSprite[0] = new AnimatedSprite(0, GROUND_Y, ResourceManager.getInstance().getHeroIdleAnimations()[0], MainBoardScreen.getScm().getVertexBufferObjectManager());
        heroIdleSprite[1] = new AnimatedSprite(0, GROUND_Y, ResourceManager.getInstance().getHeroIdleAnimations()[1], MainBoardScreen.getScm().getVertexBufferObjectManager());
        heroIdleSprite[2] = new AnimatedSprite(0, GROUND_Y, ResourceManager.getInstance().getHeroIdleAnimations()[2], MainBoardScreen.getScm().getVertexBufferObjectManager());
        heroIdleSprite[3] = new AnimatedSprite(0, GROUND_Y, ResourceManager.getInstance().getHeroIdleAnimations()[3], MainBoardScreen.getScm().getVertexBufferObjectManager());
        heroIdleSprite[0].setAnchorCenter(0.4f, 0);
        heroIdleSprite[1].setAnchorCenter(0.4f, 0);
        heroIdleSprite[2].setAnchorCenter(0.4f, 0);
        heroIdleSprite[3].setAnchorCenter(0.4f, 0);
    }

    public void moveHeroToLevelStart() {
        TilesGameCycle.getInstance().setGameRunning(false);
        Log.i("loony", "called moveHeroToLevelStart()");
        TouchAndMoveController.lockTouchActions();
        heroMovingSprite.setAlpha(1);
        heroMovingSprite.registerEntityModifier(new MoveXModifier(getHeroMovingToStartDuration(), TimeLine.LANDSCAPE_ITEM_WIDTH * -1, getHeroLevelStartX(), new IEntityModifier.IEntityModifierListener() {
            @Override
            public void onModifierStarted(IModifier<IEntity> pModifier, IEntity pItem) {
                Log.i("loony", "started hero animation!!!");
                //startHeroWalkingAnimation();

            }

            @Override
            public void onModifierFinished(IModifier<IEntity> pModifier, IEntity pItem) {
                startHeroIdleAnimation();
                LevelController.getInstance().onLevelStarted();
            }
        }) {
            @Override
            protected void onManagedUpdate(float pSecondsElapsed, IEntity pItem) {
                super.onManagedUpdate(pSecondsElapsed, pItem);
            }
        });
    }

    public float getHeroMovingToStartDuration() {
        return getHeroLevelStartX() / TimeLine.LANDSCAPE_ITEM_WIDTH * 0.4f;
    }

    public void moveHeroIntoCastle() {
        float heroX = heroMovingSprite.getX();
        float duration = ((CAMERA_WIDTH - heroX) / TimeLine.LANDSCAPE_ITEM_WIDTH - 1) * 0.4f;
        MainBoardScreen.getInstanceEngine().registerUpdateHandler(new TimerHandler(duration * 0.5f, new ITimerCallback() {
            @Override
            public void onTimePassed(TimerHandler pTimerHandler) {
                LevelController.getInstance().onLevelCompleted();
            }
        }));
        heroMovingSprite.registerEntityModifier(new MoveXModifier(duration, heroX, CAMERA_WIDTH + TimeLine.LANDSCAPE_ITEM_WIDTH, new IEntityModifier.IEntityModifierListener() {
            @Override
            public void onModifierStarted(IModifier<IEntity> pModifier, IEntity pItem) {
                startHeroWalkingAnimation();
            }

            @Override
            public void onModifierFinished(IModifier<IEntity> pModifier, IEntity pItem) {
                startHeroIdleAnimation();
                HeroAnimationController.getInstance().detachAllArrows();
            }
        }));
    }


    public void startHeroWalkingAnimation() {
        detachAllSprites();
        //heroMovingSprite.setX(heroIdleSprite[currentIdleIndex].getX());
        MainBoardScreen.getScm().getHeroLayer().attachChild(heroMovingSprite);
        heroMovingSprite.animate(1000 / WALKING_HERO_FRAMES);
        idleSequence = 0;
    }

    public void startHeroIdleAnimation() {
        detachAllSprites();
        //show "standing hero" for some seconds
        heroIdleSprite[0].setX(heroMovingSprite.getX());
        MainBoardScreen.getScm().getHeroLayer().attachChild(heroIdleSprite[0]);
        heroIdleSprite[0].stopAnimation(0);
        heroIdleSprite[0].registerEntityModifier(new DelayModifier(5f, new IEntityModifier.IEntityModifierListener() {
            @Override
            public void onModifierStarted(IModifier<IEntity> pModifier, IEntity pItem) {
                //To change body of implemented methods use File | Settings | File Templates.
            }

            @Override
            public void onModifierFinished(IModifier<IEntity> pModifier, IEntity pItem) {
                if (!isWalking && !isShooting) {
                    animateIdleHero(heroMovingSprite.getX());
                }
            }
        }));
    }

    public void animateShootingEnemy() {
        detachAllSprites();
        isShooting = true;
        MainBoardScreen.getScm().getHeroLayer().attachChild(heroShootingSprite);
        heroShootingSprite.setX(heroMovingSprite.getX());
        MainBoardScreen.getInstanceEngine().runOnUpdateThread(new Runnable() {
            @Override
            public void run() {
                heroShootingSprite.animate(40, false, new AnimatedSprite.IAnimationListener() {
                    @Override
                    public void onAnimationStarted(AnimatedSprite pAnimatedSprite, int pInitialLoopCount) {
                        TouchAndMoveController.lockTouchActions();
                    }

                    @Override
                    public void onAnimationFrameChanged(AnimatedSprite pAnimatedSprite, int pOldFrameIndex, int pNewFrameIndex) {
                    }

                    @Override
                    public void onAnimationLoopFinished(AnimatedSprite pAnimatedSprite, int pRemainingLoopCount, int pInitialLoopCount) {
                    }

                    @Override
                    public void onAnimationFinished(AnimatedSprite pAnimatedSprite) {
                        TouchAndMoveController.unlockTouchActions();
                        isShooting = false;
                        currentIdleIndex = 0;
                        startHeroIdleAnimation();
                    }
                });
            }
        });

    }

    public void animatePlayingConcert() {
        detachAllSprites();
        isPlayingConcert = true;
        MainBoardScreen.getScm().getHeroLayer().attachChild(heroConcertSprite);
        heroConcertSprite.setX(heroMovingSprite.getX());
        ParticleUtil.performConcertNotes(heroMovingSprite.getX() + 30, heroMovingSprite.getY() + 70);
        MainBoardScreen.getInstanceEngine().runOnUpdateThread(new Runnable() {
            @Override
            public void run() {
                heroConcertSprite.animate(40, false, new AnimatedSprite.IAnimationListener() {
                    @Override
                    public void onAnimationStarted(AnimatedSprite pAnimatedSprite, int pInitialLoopCount) {
                        TouchAndMoveController.lockTouchActions();
                    }

                    @Override
                    public void onAnimationFrameChanged(AnimatedSprite pAnimatedSprite, int pOldFrameIndex, int pNewFrameIndex) {
                    }

                    @Override
                    public void onAnimationLoopFinished(AnimatedSprite pAnimatedSprite, int pRemainingLoopCount, int pInitialLoopCount) {
                    }

                    @Override
                    public void onAnimationFinished(AnimatedSprite pAnimatedSprite) {
                        TouchAndMoveController.unlockTouchActions();
                        isPlayingConcert = false;
                        currentIdleIndex = 0;
                        startHeroIdleAnimation();
                    }
                });
            }
        });

    }


    private void detachAllSprites() {
        heroIdleSprite[0].detachSelf();
        heroIdleSprite[1].detachSelf();
        heroIdleSprite[2].detachSelf();
        heroIdleSprite[3].detachSelf();
        heroMovingSprite.detachSelf();
        heroShootingSprite.detachSelf();
        heroConcertSprite.detachSelf();
    }

    private void animateIdleHero(float x) {
        detachAllSprites();

        heroIdleSprite[currentIdleIndex].setX(x);
        MainBoardScreen.getScm().getHeroLayer().attachChild(heroIdleSprite[currentIdleIndex]);
        heroIdleSprite[currentIdleIndex].animate(40, false, new AnimatedSprite.IAnimationListener() {
            @Override
            public void onAnimationStarted(AnimatedSprite pAnimatedSprite, int pInitialLoopCount) {
                //To change body of implemented methods use File | Settings | File Templates.
            }

            @Override
            public void onAnimationFrameChanged(AnimatedSprite pAnimatedSprite, int pOldFrameIndex, int pNewFrameIndex) {
                //To change body of implemented methods use File | Settings | File Templates.
            }

            @Override
            public void onAnimationLoopFinished(AnimatedSprite pAnimatedSprite, int pRemainingLoopCount, int pInitialLoopCount) {
                //To change body of implemented methods use File | Settings | File Templates.
            }

            @Override
            public void onAnimationFinished(AnimatedSprite pAnimatedSprite) {
                MainBoardScreen.getInstanceEngine().runOnUpdateThread(new Runnable() {
                    @Override
                    public void run() {
                        int oldIdleIndex = currentIdleIndex;
                        if (currentIdleIndex == 0) {
                            currentIdleIndex = 1;
                        } else if (currentIdleIndex == 2) {
                            currentIdleIndex = 3;
                        } else if (currentIdleIndex == 3) {
                            currentIdleIndex = 0;
                            idleSequence = 0;
                        } else if (currentIdleIndex == 1) {
                            final int targetEnemyIndex = LevelController.getTimeLine().getTargetEnemyIndex();
                            final AnimatedSprite targetEnemyImp = LevelController.getTimeLine().getEnemyMap().get(targetEnemyIndex);
                            boolean impTooClose = false;
                            if (targetEnemyImp != null) {
                                float distance = targetEnemyImp.getX() - heroMovingSprite.getX();
                                //LOG.i("distance to target enemy imp: " + distance);
                                if (distance < 60) {
                                    impTooClose = true;
                                }
                            }
                            if (idleSequence < 2 || impTooClose) {
                                currentIdleIndex = 0;
                                idleSequence++;
                            } else {
                                currentIdleIndex = 2;
                            }
                        }
                        heroIdleSprite[oldIdleIndex].registerEntityModifier(new DelayModifier(MathUtils.random(3, 7), new IEntityModifier.IEntityModifierListener() {
                            @Override
                            public void onModifierStarted(IModifier<IEntity> pModifier, IEntity pItem) {
                            }

                            @Override
                            public void onModifierFinished(IModifier<IEntity> pModifier, IEntity pItem) {
                                if (!isWalking && !isShooting) {
                                    animateIdleHero(heroMovingSprite.getX());
                                }
                            }
                        }));

                    }
                });

            }
        });
    }

    public void moveHeroStepRight() {
        LOG.i("Hero move step right!");
        heroMovingSprite.registerEntityModifier(new MoveXModifier(HERO_STEP_DURATION, heroMovingSprite.getX(), heroMovingSprite.getX() + TimeLine.LANDSCAPE_ITEM_WIDTH));
    }

    public float getHeroCurrentX() {
        return heroMovingSprite.getX();
    }

    public synchronized void animateWalkStep(int moves) {
        LOG.i("Requested steps forward: " + moves);
        int arrowsToReveal = 0;
        if (!isWalking) {
            LOG.i("walking false, set to true");
            isWalking = true;
            MainBoardScreen.getScm().getHeroLayer().registerEntityModifier(new DelayModifier(HERO_STEP_DURATION, new AttachingWalkAnimationModifierListener()));
            stepsPending = stepsPending + moves - 1;

            arrowsToReveal = moves - 1;
            LOG.i("Pending steps: " + stepsPending);
        } else {
            stepsPending = stepsPending + moves;
            arrowsToReveal = moves;
            LOG.i("walking true, adding stepsPending+" + moves + "= " + stepsPending);
        }
        if (stepsPending > 0) {
            //lock "current" matches before current landscape is reached
            LevelController.getTimeLine().setCurrentLandscapeReached(false);
            animateNewArrows(arrowsToReveal);
        }
    }

    private void animateNewArrows(int moves) {
        for (int i = 0; i < moves; i++) {
            //get the landscape index for the arrow to attach
            final int lsIndex = LevelController.getTimeLine().getCurrentItemIndex() - moves + i + 1;
            if (lsIndex >= LevelController.getTimeLine().getLevelItems().size()) {
                break;
            }
            final AnimatedSprite arrow = LevelController.getTimeLine().getArrows().get(lsIndex);
            arrow.setVisible(true);
            arrow.registerEntityModifier(
                    new DelayModifier(0.3f * i, new IEntityModifier.IEntityModifierListener() {
                        @Override
                        public void onModifierStarted(IModifier<IEntity> pModifier, IEntity pItem) {
                        }

                        @Override
                        public void onModifierFinished(IModifier<IEntity> pModifier, IEntity pItem) {
                            LOG.i("arrows", "revealing and animating arrow for index" + lsIndex);
                            arrow.setAlpha(1f);
                            arrow.animate(new long[]{50, 50, 50, 50, 50, 50, 50, 50}, false);
                        }
                    }));
        }
    }

    public boolean isWalking() {
        return isWalking;
    }

    public int getStepsPending() {
        return stepsPending;
    }

    public synchronized void decreaseStepsPending(int steps) {
        this.stepsPending -= steps;
    }

    public void setWalking(boolean walking) {
        isWalking = walking;
    }

    public void detachAllArrows() {
        for (int i = 0; i < LevelController.getCurrentLevel().getLength(); i++) {
            final AnimatedSprite arrow = LevelController.getTimeLine().getArrows().get(i);
            if (arrow != null) {
                arrow.detachSelf();
            }
        }
    }

    public float getHeroLevelStartX() {
        return LevelController.getTimeLine().getStartLsX() + TimeLine.LANDSCAPE_ITEM_WIDTH / 2f;
    }

    public Runnable getAbilityActionRequested() {
        return abilityActionRequested;
    }

    public void setAbilityActionRequested(Runnable abilityActionRequested) {
        this.abilityActionRequested = abilityActionRequested;
    }
}
